<!--
 * @description: 通用树型下拉框
 * @fileName: treeSelect.vue
 * @author: t
 * @date: 2023-03-07 17:15:03
 * @Attributes: data	展示数据	array
                props	配置选项，具体配置可以参照element ui库中el-tree的配置	object
                show-checkbox	节点是否可被选择	boolean
                check-strictly	在显示复选框的情况下，是否严格的遵循父子不互相关联的做法，默认为 false	boolean
                icon-class	自定义树节点的图标	string
                load	加载子树数据的方法，仅当 lazy 属性为true 时生效	function(node, resolve)
                lazy	是否懒加载子节点，需与 load 方法结合使用	boolean
                disabled	下拉框是否禁用
                getCheckedKeys	若节点可被选择（即 show-checkbox 为 true），则返回目前被选中的节点的 key 所组成的数组
                getCurrentNode	获取当前被选中节点的 data，若没有节点被选中则返回 null
                collapse-tags	多选时是否将选中值按文字的形式展示
                select-last-node	单选时是否只能选择最后一个节点
                Scoped Slot 自定义树节点的内容，参数为 { node, data }
                show-count 若节点中存在children 则在父节点展示所属children的数量，注意但设置插槽时 show-count将失效！
                clearable	单选时是否可以清空选项
                filterable 属性即可启用搜索功
                !!!!!必传!!!!!! props-value	每个树节点用来作为唯一标识的属性，整棵树应该是唯一的 !!!!!!
!-->
<!--:clearable="$attrs['show-checkbox']==undefined?$attrs['clearable']:false"-->
<template>
  <el-select
    ref="mySelect"
    :value="valueFilter(value)"
    :placeholder="$attrs['placeholder']"
    :multiple="$attrs['show-checkbox']"
    :disabled="$attrs['disabled']"
    :filterable="$attrs['filterable']"
    :clearable="$attrs['clearable']"
    :collapse-tags="$attrs['collapse-tags']"
    :filter-method="remoteMethod"
    @change="selectChange"
    @clear="selectClear"
  >
    <template slot="empty">
      <div class="selectTree">
        <el-tree
          ref="myTree"
          :data="data"
          :props="props"
          :show-checkbox="$attrs['show-checkbox']"
          :check-strictly="$attrs['check-strictly']"
          :icon-class="$attrs['icon-class']"
          :lazy="$attrs['lazy']"
          :load="$attrs['load']"
          :node-key="props.value"
          :filter-node-method="filterNode"
          :default-expanded-keys="defaultExpandedKeys"
          @node-click="handleNodeClick"
          @check-change="handleCheckChange"
        >
          <!-- eslint-disable-next-line -->
          <template slot-scope="{ node, data }">
            <slot :node="node" :data="data">
              <span class="slotSpan">
                <span>
                  {{ data[props.label] }}
                  <b v-if="$attrs['show-count'] !== undefined && data[props.children]">（{{ data[props.children].length }}）</b>
                </span>
              </span>
            </slot>
          </template>
        </el-tree>
      </div>
    </template>
  </el-select>
</template>

<script>
export default {
  props: {
    value: {
      type: undefined,
      default: null
    },
    data: {
      type: Array,
      default: null
    },
    props: {
      type: Object,
      default: null
    }
  },
  data() {
    return {
      defaultExpandedKeys: []
    }
  },

  watch: {
    value: {
      deep: true,
      handler(val) {
        if (!val || !val?.length) {
          this.selectClear(true)
        }
      }
    }
  },
  created() {
    this.propsInit()
  },
  mounted() {
    setTimeout(this.initData, 10)
  },
  beforeUpdate() {
    this.propsInit()
    this.initData()
  },

  methods: {
    initData() {
      if (this.$attrs['show-checkbox'] === undefined) {
        const newItem = this.recurrenceQuery(this.data, this.props.value, this.value)
        if (newItem.length) {
          if (this.props.value && newItem[0][this.props.value]) {
            this.defaultExpandedKeys = [newItem[0][this.props.value]]
          }
          this.$nextTick(() => {
            this.$refs.myTree.setCurrentNode(newItem[0])
          })
        }
      } else {
        let newValue = JSON.parse(JSON.stringify(this.value))
        if (!(newValue instanceof Array)) {
          newValue = [newValue]
        }
        if (newValue?.length) {
          const checkList = newValue.map(key => {
            if (key) {
              const newItem = this.recurrenceQuery(this.data, this.props.value, key)
              return newItem[0] || ''
            }
          })
          if (checkList?.length) {
            const defaultExpandedKeys = checkList.map(item => item?.[this.props.value || ''])
            if (defaultExpandedKeys.length) this.defaultExpandedKeys = defaultExpandedKeys
            this.$nextTick(() => {
              this.$refs.myTree.setCheckedNodes(checkList)
            })
          }
        }
      }
      this.$forceUpdate()
    },
    // 多选
    handleCheckChange(data, e, ev) {
      const checkList = this.$refs.myTree.getCheckedNodes()
      let setList = null
      if (checkList.length) {
        setList = checkList.map(item => item[this.props.value])
      }
      this.$emit('input', setList)
      // 共三个参数，依次为：传递给 data 属性的数组中该节点所对应的对象、节点本身是否被选中、节点的子树中是否有被选中的节点
      this.$emit('change', data, e, ev)
    },
    // 单选事件
    handleNodeClick(data, e) {
      if (!(this.$attrs['select-last-node'] === undefined)) {
        if (data[this.props.children] && data[this.props.children]?.length) {
          return false
        }
      }
      if (this.$attrs['show-checkbox'] === undefined) {
        this.$emit('input', data[this.props.value])
        this.$refs.mySelect.blur()
      }
      this.$emit('change', data, e)
    },
    //   递归查找通用方法
    recurrenceQuery(list, key, value) {
      if (!list || !key || !value) return []
      const queryData = []
      list.map(item => {
        item.id = item.id.toString()
        item.parentId = item.parentId.toString()
        if (item[this.props.children] && item[this.props.children].length) {
          queryData.push(...this.recurrenceQuery(item[this.props.children], key, value))
        }
        if (item[key] === value) {
          queryData.push(item)
        }
        return item
      })
      return queryData
    },
    selectChange(e) {
      if (this.$attrs['show-checkbox'] !== undefined) {
        const checkList = e.map(key => {
          const newItem = this.recurrenceQuery(this.data, this.props.label, key)
          return newItem[0] || ''
        })
        this.$refs.myTree.setCheckedNodes(checkList)
        this.$emit('input', e)
      }
    },
    selectClear(flag) {
      if (this.$attrs['show-checkbox'] === undefined) {
        if (!flag) this.$emit('input', '')
        this.$refs.myTree.setCurrentKey(null)
      } else {
        if (!flag) this.$emit('input', [])
        this.$refs.myTree.setCheckedKeys([])
      }
      this.remoteMethod('')
    },
    getCheckedNodes() {
      if (this.value !== null && this.value !== undefined && this.value !== '') {
        return this.$refs.myTree.getCheckedNodes()
      }
      return []
    },
    getCurrentNode() {
      if (this.value !== null && this.value !== undefined && this.value !== '') {
        return this.$refs.myTree.getCurrentNode()
      }
      return null
    },
    valueFilter(val) {
      if (this.$attrs['show-checkbox'] === undefined) {
        let res = '';
        [res] = this.recurrenceQuery(this.data, this.props.value, val)
        return res?.[this.props.label] || ''
      } else {
        if (!val?.length) return []
        let res = val.map(item => {
          const [newItem] = this.recurrenceQuery(this.data, this.props.value, item)
          return newItem?.[this.props.label] || ''
        })
        if (!res?.length) return []
        res = res.filter(item => item)
        return res
      }
    },
    propsInit() {
      this.props.label = this.props.label || 'label'
      this.props.value = this.props.value || 'value'
      this.props.children = this.props.children || 'children'
      if (this.$attrs['select-last-node'] !== undefined && !this.props.disabled) {
        this.props.disabled = data => data?.[this.props.children]?.length
        this.$attrs['check-strictly'] = true
      }
    },

    remoteMethod(query) {
      this.$refs.myTree.filter(query)
    },
    filterNode(value, data) {
      if (!value) return true
      return data[this.props.label].indexOf(value) !== -1
    }
  }
}
</script>

<style lang="scss" scoped>
.selectTree {
  max-height: 50vh;
  overflow: auto;
  padding: 5px;
}

.el-select {
  width: 100%;
}

.slotSpan {
  font-size: 14px;

  b {
    font-weight: normal;
    font-size: 12px;
    color: #999;
  }
}

.selectTree ::v-deep .el-tree-node__content {
  font-size: 14px;
}
</style>
